package sel.me.spring.boot159.web.hystrix;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import lombok.extern.slf4j.Slf4j;
import sel.me.spring.boot159.web.service.HystrixTestService;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * https://blog.csdn.net/loushuiyifan/article/details/82702522
 *
 * Hystrix整个工作流如下：
 *
 * 构造一个 HystrixCommand或HystrixObservableCommand对象，用于封装请求，并在构造方法配置请求被执行需要的参数；
 * 执行命令，Hystrix提供了4种执行命令的方法，后面详述；
 * 判断是否使用缓存响应请求，若启用了缓存，且缓存可用，直接使用缓存响应请求。Hystrix支持请求缓存，但需要用户自定义启动；
 * 判断熔断器是否打开，如果打开，跳到第8步；
 * 判断线程池/队列/信号量是否已满，已满则跳到第8步；
 * 执行HystrixObservableCommand.construct()或HystrixCommand.run()，如果执行失败或者超时，跳到第8步；否则，跳到第9步；
 * 统计熔断器监控指标；
 * 走Fallback备用逻辑
 * 返回请求响应
 *
 * ==================================================
 *
 * 执行命令的几种方法
 * Hystrix提供了4种执行命令的方法，execute()和queue() 适用于HystrixCommand对象，而observe()和toObservable()适用于HystrixObservableCommand对象。
 *
 * execute()
 *
 * 以同步堵塞方式执行run()，只支持接收一个值对象。hystrix会从线程池中取一个线程来执行run()，并等待返回值。
 *
 * queue()
 *
 * 以异步非阻塞方式执行run()，只支持接收一个值对象。调用queue()就直接返回一个Future对象。可通过 Future.get()拿到run()的返回结果，但Future.get()是阻塞执行的。若执行成功，Future.get()返回单个返回值。当执行失败时，如果没有重写fallback，Future.get()抛出异常。
 *
 * observe()
 *
 * 事件注册前执行run()/construct()，支持接收多个值对象，取决于发射源。调用observe()会返回一个hot Observable，也就是说，调用observe()自动触发执行run()/construct()，无论是否存在订阅者。
 *
 * 如果继承的是HystrixCommand，hystrix会从线程池中取一个线程以非阻塞方式执行run()；如果继承的是HystrixObservableCommand，将以调用线程阻塞执行construct()。
 *
 * observe()使用方法：
 *
 * 调用observe()会返回一个Observable对象
 * 调用这个Observable对象的subscribe()方法完成事件注册，从而获取结果
 * toObservable()
 *
 * 事件注册后执行run()/construct()，支持接收多个值对象，取决于发射源。调用toObservable()会返回一个cold Observable，也就是说，调用toObservable()不会立即触发执行run()/construct()，必须有订阅者订阅Observable时才会执行。
 *
 * 如果继承的是HystrixCommand，hystrix会从线程池中取一个线程以非阻塞方式执行run()，调用线程不必等待run()；如果继承的是HystrixObservableCommand，将以调用线程堵塞执行construct()，调用线程需等待construct()执行完才能继续往下走。
 *
 * toObservable()使用方法：
 *
 * 调用observe()会返回一个Observable对象
 * 调用这个Observable对象的subscribe()方法完成事件注册，从而获取结果
 * 需注意的是，HystrixCommand也支持toObservable()和observe()，但是即使将HystrixCommand转换成Observable，它也只能发射一个值对象。只有HystrixObservableCommand才支持发射多个值对象。
 *
 * @author shenenlu 2021年05月26日 下午14:46:34
 */
@Slf4j
public class HystrixTestCommand extends HystrixCommand<Integer> {

    private static AtomicInteger orderIdCounter = new AtomicInteger(0);

    private HystrixTestService hystrixTestService;

    public HystrixTestCommand(HystrixTestService hystrixTestService) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hystrixTestService"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("queryByOrderId"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withCircuitBreakerRequestVolumeThreshold(10)//至少有10个请求，熔断器才进行错误率的计算
                        .withCircuitBreakerSleepWindowInMilliseconds(5000)//熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试
                        .withCircuitBreakerErrorThresholdPercentage(50)//错误率达到50开启熔断保护
                        .withExecutionTimeoutInMilliseconds(500)
                        .withExecutionTimeoutEnabled(true)
                )
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties
                        .Setter().withCoreSize(10)));
        this.hystrixTestService = hystrixTestService;
    }

    @Override
    protected Integer run() {
        int c = orderIdCounter.getAndIncrement();
        if ( c < 10 ) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return hystrixTestService.queryByOrderId();
    }


    @Override
    protected Integer getFallback() {
        return -1;
    }
}
